#######################################################
# The class of Coupled DEVS  
# Author: moonho.hwang@gmail.com
#   Date: Oct.19.2011
#######################################################
from Simulatable import *

class CoupledDEVS(Simulatable):
   '''the coupled DEVS class'''
   def __init__(self, name, parent=None, tl=0, tn=float('inf')):
      Simulatable.__init__(self, name, parent, tl, tn)
      self.M = list()
      self.Z = dict()
      self.IMM = set()

   def addComponents(self, *args):
      '''add a component <i> com </i>'''
      for com in args:
         if isinstance(com,Simulatable) and com not in self.M:
            self.M.append(com)   # add com into M
            com.parent = self # set com's parent to me

   def addCouple(self, src_port, dest_port):
      '''add couples connecting <i> src_port</i> to <i> dest_port</i>'''
      if isinstance(src_port, Port) and isinstance(dest_port, Port):
         if src_port not in self.Z:
            self.Z[src_port]=set() 

         self.Z[src_port].add(dest_port)
      else:
         raise Exception("{0} and {1} not Port".format(src_port, dest_port))

   def addCouples(self, *args):
      '''add couples connecting args = {(src_port, dest_port)}'''
      for src_port, dest_port in args:
         self.addCouple(src_port, dest_port)

   def update_tN(self, update_parent = False):
      '''update IMM and timeNext'''
      self.set_timeNext(float("inf"))
      self.IMM = set() 
      for m in self.M:
         if m.timeNext() < self.timeNext():
            self.IMM = set(); self.IMM.add(m)
            self.set_timeNext(m.timeNext() )
         elif m.timeNext() == self.timeNext():
            self.IMM.add(m)
      if update_parent and self.parent != None:
         self.parent.update_tN(update_parent = True)
   def Delta_0(self, t) :
      '''virtual function Delta_0: making the initial state'''
      self.set_timeLast(t)
      for m in self.M: 
         m.Delta_0(t) 
      self.update_tN()

   def Delta_x(self, x, t):
      '''the simulator function invoking when it receives an input event x at time t'''
      self.set_timeLast(t)
      if isinstance(x, PortValue):
         for dest_p in self.Z[x.port]:
            if dest_p.parent != None: 
               dest_p.parent.Delta_x(PortValue(dest_p, x.value),t) 
      else:
         raise Exception("%s is not PortValue!"%x)
      self.update_tN()

   def translateOutputEvent(self, y,t):
      '''when receive a set of output events (port, value) from a subcomponent'''
      oy = []
      if y and isinstance(y, PortValue):
         if y.port in self.Z:
            for dest_p in self.Z[y.port]:
               if isinstance(dest_p, InputPort):
                  if dest_p.parent != None:
                     dest_p.parent.Delta_x(PortValue(dest_p, y.value),t) 
               elif isinstance(dest_p, OutputPort):
                  if dest_p.parent != None and isinstance(dest_p.parent, CoupledDEVS):
                     for z in dest_p.parent.translateOutputEvent(PortValue(dest_p, y.value),t):
                        oy.append(z)
         else: # top most 
            oy.append(y) 
      return oy

   def getAllInfluencee(self, z, XP):
      '''get all influencee input ports of output event (port, value) or port'''
      zp = None
      if isinstance(z, PortValue):
         zp = z.port
      if isinstance(z, Port):
         zp = z
      if zp in self.Z:
         for dest_p in self.Z[zp]:
            if isinstance(dest_p.parent, CoupledDEVS) :
               dest_p.parent.getAllInfluencee(dest_p, XP)
            elif isinstance(dest_p.parent, Simulatable): # atomic Simulatable, that's the terminal
               XP.add(dest_p)

   def Delta_y(self, t):
      '''the simulator function invoking when it needs to generate an output event y at time t'''
      self.set_timeLast(t)
      ys = []
      if len(self.IMM) > 0 : 
         Y = self.IMM.pop().Delta_y(t) # send to the IMM component
         if not hasattr(Y, '__iter__'): Y = [Y]
         for z in Y:   
            oys = self.translateOutputEvent(z,t)
            for y in oys:
               if y and y.port.parent == self: ys.append(y)

         self.update_tN()
      return ys
   
   def Delta_null(self, t):
      '''update the state including statistics at time t'''
      for m in self.M:
         m.Delta_null(t)

   def get_statistics_str(self):
      '''get the string of statistics'''
      s = self.name+"\n"
      for m in self.M:
         if isinstance(m, CoupledDEVS):
            s +=m.get_statistics_str() + "\n"
         else:
            s += "\t"+m.name+": "+ m.get_statistics_str() + "\n"
      return s

   def to_Z_s(self):
      '''convert couplings to a string'''
      s = "Z={"
      k=0 
      for src_port in self.Z.keys(): 
         if(k>0): s = s + ", " 
         k+=1
         s += src_port.parent_hier_name_to_s() + "=>["
         i =0
         for dest in self.Z[src_port]:
            if(i>0): s += ", " 
            i+=1
            s += dest.parent_hier_name_to_s()
         s += "]"
      s += "}"
      return s

   def __str__(self):
      '''convert this model to a string'''
      s = Simulatable.__str__(self) 
      s += "M=" + str(self.M) + "\n"
      s += self.to_Z_s()
      return s

   def state_to_s(self):
      '''# convert the subcomponents' states to a string'''
      i = 0
      s = "("
      for m in self.M:
         if i > 0:
            s = s + ", " 
         else:  i = 1 
         s = s + m.qstate_to_s()
      s = s + ")"
      return s 

   def isNetworkOf(self, cclass):
      '''If each child m in M, m is a network of (ctype), return True'''
      if isinstance(self, cclass): return True
      for m in self.M:
         if isinstance(m, CoupledDEVS) :
            if m.isNetWorkOf(cclass) == False: return False
      return True
#   def qstate_to_s(self):
#      '''convert the current state to a string'''
#      s = str(self.name)
#      s = s + ", " + self.M_qstates_to_s()
#      s = s + ", " + str(self.clock);
#      return s

if __name__ == '__main__':
   md = CoupledDEVS("cp")
   md.addInputPorts("ip")
   md.addOutputPorts("out")

   print md
